package com.gao.weblog.web.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gao.weblog.common.domain.dos.ArticleCategoryRelDO;
import com.gao.weblog.common.domain.dos.ArticleDO;
import com.gao.weblog.common.domain.dos.ArticleTagRelDO;
import com.gao.weblog.common.domain.dos.CategoryDO;
import com.gao.weblog.common.domain.dos.TagDO;
import com.gao.weblog.common.domain.mapper.ArticleCategoryRelMapper;
import com.gao.weblog.common.domain.mapper.ArticleContentMapper;
import com.gao.weblog.common.domain.mapper.ArticleMapper;
import com.gao.weblog.common.domain.mapper.ArticleTagRelMapper;
import com.gao.weblog.common.domain.mapper.CategoryMapper;
import com.gao.weblog.common.domain.mapper.TagMapper;
import com.gao.weblog.common.enums.RedisKeyPrefix;
import com.gao.weblog.common.enums.ResponseCodeEnum;
import com.gao.weblog.common.model.dto.ArticleQueryDTO;
import com.gao.weblog.common.utils.PageResponse;
import com.gao.weblog.common.utils.Response;
import com.gao.weblog.web.constant.RabbitConst;
import com.gao.weblog.web.convert.ArticleConvert;
import com.gao.weblog.web.markdown.MarkdownHelper;
import com.gao.weblog.web.module.vo.article.FindIndexArticleDetailReqVO;
import com.gao.weblog.web.module.vo.article.FindIndexArticleDetailRspVO;
import com.gao.weblog.web.module.vo.article.FindIndexArticlePageListReqVO;
import com.gao.weblog.web.module.vo.article.FindIndexArticlePageListRspVO;
import com.gao.weblog.web.module.vo.article.FindPreNextArticleRspVO;
import com.gao.weblog.web.module.vo.category.FindCategoryListRspVO;
import com.gao.weblog.web.module.vo.tag.FindTagListRspVO;
import com.gao.weblog.web.service.ArticleService;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ArticleServiceImpl implements ArticleService {

    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private TagMapper tagMapper;
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private ArticleCategoryRelMapper articleCategoryRelMapper;
    @Autowired
    private ArticleTagRelMapper articleTagRelMapper;
    @Autowired
    private ArticleContentMapper articleContentMapper;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Response findArticlePageList(FindIndexArticlePageListReqVO findIndexArticlePageListReqVO) {

        Page<ArticleDO> articleDOPage = getArticleDOPage(findIndexArticlePageListReqVO);
        if (articleDOPage.getSize()==0) {
            return PageResponse.success(articleDOPage, null);
        }
        List<ArticleDO> articleDOS = articleDOPage.getRecords();
        List<FindIndexArticlePageListRspVO> findIndexArticlePageListRspVOS = articleDOS.stream()
                .map(articleDO -> ArticleConvert.INSTANCE.convertDO2VO(articleDO))
                .collect(Collectors.toList());
        List<Long> articleIds = articleDOS.stream().map(ArticleDO::getId).collect(Collectors.toList());
        processCategory(articleIds, findIndexArticlePageListRspVOS);
        processTags(articleIds, findIndexArticlePageListRspVOS);
        return PageResponse.success(articleDOPage, findIndexArticlePageListRspVOS);
    }

    private void processTags(List<Long> articleIds, List<FindIndexArticlePageListRspVO> findIndexArticlePageListRspVOS) {
        Map<Long, String> tagIdNameMap = tagMapper.selectList(Wrappers.emptyWrapper()).stream().collect(Collectors.toMap(TagDO::getId, TagDO::getName));
        Map<Long, List<Long>> groupTagIdByArticleId = groupTagIdByArticleId(articleIds);
        for (FindIndexArticlePageListRspVO vo : findIndexArticlePageListRspVOS) {
            List<Long> tagIds = groupTagIdByArticleId.get(vo.getId());
            if (CollectionUtils.isEmpty(tagIds)) {
                continue;
            }
            vo.setTags(getFindTagListRspVOS(tagIds, tagIdNameMap));
        }
    }

    @NotNull
    private static List<FindTagListRspVO> getFindTagListRspVOS(List<Long> tagIds, Map<Long, String> mapIdNameMap) {
        List<FindTagListRspVO> findTagListRspVOS = new ArrayList<>(tagIds.size());
        for (Long tagId : tagIds) {
            String tagName = mapIdNameMap.get(tagId);
            if (tagName==null){
                continue;
            }
            FindTagListRspVO findTagListRspVO = FindTagListRspVO.builder()
                    .id(tagId)
                    .name(tagName)
                    .build();
            findTagListRspVOS.add(findTagListRspVO);
        }
        return findTagListRspVOS;
    }

    private void processCategory(List<Long> articleIds, List<FindIndexArticlePageListRspVO> findIndexArticlePageListRspVOS) {
        Map<Long, String> categoryIdNameMap = getCategoryIdNameMap();
        Map<Long, Long> articleCategoryRelMap = getArticleCategoryRelMap(articleIds);

        for (FindIndexArticlePageListRspVO vo : findIndexArticlePageListRspVOS) {
            Long categoryId= articleCategoryRelMap.get(vo.getId());
            if (categoryId==null){
                continue;
            }
            String categoryName = categoryIdNameMap.get(categoryId);
            if (categoryName==null){
                continue;
            }
            FindCategoryListRspVO findCategoryListRspVO = FindCategoryListRspVO.builder()
                    .id(categoryId)
                    .name(categoryName)
                    .build();
            vo.setCategory(findCategoryListRspVO);
        }
    }

    @NotNull
    private Map<Long, Long> getArticleCategoryRelMap(List<Long> articleIds) {
        List<ArticleCategoryRelDO> articleCategoryRelDOS = articleCategoryRelMapper.selectByArticleIds(articleIds);
        Map<Long, Long> articleCategoryRelMap = articleCategoryRelDOS.stream().collect(Collectors.toMap(ArticleCategoryRelDO::getArticleId, ArticleCategoryRelDO::getCategoryId));
        return articleCategoryRelMap;
    }

    @NotNull
    private Map<Long, List<Long>> groupTagIdByArticleId(List<Long> articleIds) {
        List<ArticleTagRelDO> articleTagRelDOS = articleTagRelMapper.selectByArticleIds(articleIds);
        Map<Long, List<Long>> groupTagIdByArticleId = articleTagRelDOS.stream().collect(Collectors.groupingBy(ArticleTagRelDO::getArticleId, Collectors.mapping(ArticleTagRelDO::getTagId, Collectors.toList())));
        return groupTagIdByArticleId;
    }

    @NotNull
    private Map<Long, String> getCategoryIdNameMap() {
        List<CategoryDO> categoryDOS = categoryMapper.selectList(Wrappers.emptyWrapper());
        Map<Long, String> categoryIdNameMap = categoryDOS.stream().collect(Collectors.toMap(CategoryDO::getId, CategoryDO::getName));
        return categoryIdNameMap;
    }

    private Page<ArticleDO> getArticleDOPage(FindIndexArticlePageListReqVO findIndexArticlePageListReqVO) {
        Long current = findIndexArticlePageListReqVO.getCurrent();
        Long size = findIndexArticlePageListReqVO.getSize();

        ArticleQueryDTO articleQueryDTO = new ArticleQueryDTO();
        articleQueryDTO.setSize(size);
        articleQueryDTO.setCurrent(current);
        Page<ArticleDO> articleDOPage = articleMapper.selectPageList(articleQueryDTO);
        return articleDOPage;
    }

    @Override
    public Response findArticleDetail(FindIndexArticleDetailReqVO findIndexArticleDetailReqVO) {
        Long articleId = findIndexArticleDetailReqVO.getArticleId();
        ArticleDO articleDO = articleMapper.selectById(articleId);

        Assert.isNull(articleDO, ResponseCodeEnum.ARTICLE_NOT_FOUND.getErrorMessage());

        FindIndexArticleDetailRspVO vo = FindIndexArticleDetailRspVO.builder()
                .title(articleDO.getTitle())
                .createTime(articleDO.getCreateTime())
                .readNum(articleDO.getReadNum())
                .build();

        String content = getContent(articleId);
        vo.setContent(MarkdownHelper.convertMarkdownToHtml(content));

        CategoryDO categoryDO = articleCategoryRelMapper.selectCategoryByArticleId(articleId);
        vo.setCategoryId(categoryDO.getId());
        vo.setCategoryName(categoryDO.getName());

        List<TagDO> tagDOS = articleTagRelMapper.selectTagsByArticleId(articleId);
        List<FindTagListRspVO> findTagListRspVOS = Lists.newArrayList();
        tagDOS.forEach(tagDO -> {
            findTagListRspVOS.add(FindTagListRspVO.builder().id(tagDO.getId()).name(tagDO.getName()).build());
        });

        ArticleDO preArticle = articleMapper.selectPreArticle(articleId);
        if (Objects.nonNull(preArticle)) {
            vo.setPreArticle(FindPreNextArticleRspVO.builder()
                    .articleId(preArticle.getId())
                    .articleTitle(preArticle.getTitle())
                    .build());
        }

        ArticleDO nextArticle = articleMapper.selectNextArticle(articleId);
        if (Objects.nonNull(nextArticle)) {
            vo.setNextArticle(FindPreNextArticleRspVO.builder()
                    .articleId(nextArticle.getId())
                    .articleTitle(nextArticle.getTitle())
                    .build());
        }
        log.info("========================" + articleId);

        rabbitTemplate.convertAndSend(RabbitConst.READ_NUM_EXCHANGE, RabbitConst.READ_NUM_ROUTING, articleId);

        return Response.success(vo);


    }

    @NotNull
    private String getContent(Long articleId) {
        String contentKey = RedisKeyPrefix.ARTICLE_CONTENT.getPrefix() + articleId;
        String content = (String) redisTemplate.opsForValue().get(contentKey);

        if (StringUtils.isEmpty(content)) {
            content = articleContentMapper.selectByArticleId(articleId);
            redisTemplate.opsForValue().set(contentKey, content, Duration.ofDays(1));
        }
        return content;
    }
}

